package com.linkhub.oms.admin.modules.stock.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.github.yulichang.toolkit.JoinWrappers;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.google.common.collect.Lists;
import com.linkhub.oms.admin.common.enums.StockSortFieldEnum;
import com.linkhub.oms.admin.common.exception.ExceptionFactory;
import com.linkhub.oms.admin.modules.order.service.OrderInfoAssembleService;
import com.linkhub.oms.admin.modules.stock.compute.StockComputeExecutor;
import com.linkhub.oms.admin.modules.stock.compute.StockCreator;
import com.linkhub.oms.admin.modules.stock.domain.dto.PassageStockDTO;
import com.linkhub.oms.admin.modules.stock.domain.dto.StockPreemptionResultDTO;
import com.linkhub.oms.admin.modules.stock.domain.param.StockFlowQueryParam;
import com.linkhub.oms.admin.modules.stock.domain.param.StockQueryParam;
import com.linkhub.oms.admin.modules.stock.domain.vo.StockFlowVO;
import com.linkhub.oms.admin.modules.stock.domain.vo.StockVO;
import com.linkhub.oms.admin.modules.stock.service.StockBizService;
import com.linkhub.oms.admin.modules.wms.domain.dto.WarehouseArrantConfirmDTO;
import com.linkhub.oms.common.enums.IEnumConvert;
import com.linkhub.oms.common.enums.mp.func.StockFuncEnum;
import com.linkhub.oms.common.response.PageDTO;
import com.linkhub.oms.common.util.CacheKeyAssist;
import com.linkhub.oms.common.util.QtOptionalUtil;
import com.linkhub.oms.common.util.QtStreamUtil;
import com.linkhub.oms.component.mp.util.QtMybatisPlusUtil;
import com.linkhub.oms.component.redis.locker.DistributedLocker;
import com.linkhub.oms.dao.mapper.LocalProductEntityMapper;
import com.linkhub.oms.dao.mapper.PhysicalStockFlowEntityMapper;
import com.linkhub.oms.dao.mpservice.IProductStockService;
import com.linkhub.oms.entity.LocalProductEntity;
import com.linkhub.oms.entity.OrderProductEntity;
import com.linkhub.oms.entity.ProductStockEntity;
import com.linkhub.oms.entity.UserEntity;
import com.linkhub.oms.entity.resulttype.stock.ProductStockResultEntity;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @Description: StockBizService
 * @Author: echo
 * @Date: 2024/4/25 19:16
 * @Version: 1.0
 */
@Slf4j
@Service
public class StockBizServiceImpl implements StockBizService {

    @Resource
    private LocalProductEntityMapper localProductEntityMapper;
    @Resource
    private DistributedLocker distributedLocker;
    @Resource
    private OrderInfoAssembleService orderInfoAssembleService;
    @Resource
    private IProductStockService productStockService;
    @Resource
    private PhysicalStockFlowEntityMapper physicalStockFlowMapper;

    @Override
    public PageDTO<StockVO> pageStocks(StockQueryParam stockQueryParam) {
        MPJLambdaWrapper<LocalProductEntity> stockQueryWrapper = createStockQueryWrapper(stockQueryParam);

        Page<ProductStockResultEntity> objectPage = new Page<>(stockQueryParam.getPage(), stockQueryParam.getSize());
        objectPage.setOptimizeJoinOfCountSql(false);

        Page<ProductStockResultEntity> page = localProductEntityMapper.selectJoinPage(objectPage,
                ProductStockResultEntity.class, stockQueryWrapper);

        return QtMybatisPlusUtil.convertPage(page, StockVO::convert);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<StockPreemptionResultDTO> preemptOrderLocalProductStocks( List<String> orderIds) {
        Map<String, List<OrderProductEntity>> orderLocalSkuMap = orderInfoAssembleService.queryOrderLocalSkuIds(orderIds);

        // 根据skuIds 加锁
        List<String> localSkuIds = QtStreamUtil.flatMapAndDistinct(orderLocalSkuMap.values(),
                list -> QtStreamUtil.map(list, OrderProductEntity::getSkuId).stream());
        List<String> skuStockKeys = QtStreamUtil.map(localSkuIds, CacheKeyAssist::getProductPreemptionStockLockKey);
        if (!distributedLocker.tryLockMulti(skuStockKeys)) {
            throw ExceptionFactory.getI18nBizException("order.sync.concurrence.error");
        }

        List<StockPreemptionResultDTO> res = Lists.newArrayListWithCapacity(orderIds.size());
        try {
            // 查询 skuIds 的库存
            List<ProductStockEntity> stockList = productStockService.listBySkuIds(localSkuIds);
            if (CollUtil.isEmpty(stockList)) {
                return QtStreamUtil.map(orderIds, StockPreemptionResultDTO::createFailResult);
            }

            // 创建预占库存计算器
            StockComputeExecutor executor = StockComputeExecutor
                    .create(StockCreator.create(stockList))
                    .preemption();

            orderLocalSkuMap.forEach((sysOrderId, orderProducts) -> {
                Map<String, Integer> skuCountMap = QtStreamUtil.toMap(orderProducts,
                        OrderProductEntity::getSkuId,
                        OrderProductEntity::getCurrentQuantity);
                // 批量执行预占每个订单的所有sku库存
                boolean preemption = executor.executeMultiPreemption(skuCountMap);

                StockPreemptionResultDTO stockPreemptionResultDTO = preemption
                        ? StockPreemptionResultDTO.createSuccessResult(sysOrderId)
                        : StockPreemptionResultDTO.createFailResult(sysOrderId);

                res.add(stockPreemptionResultDTO);

            });

            Map<String, Integer> preemptionStockMap = executor.getSkuPreemptionStockMap();
            if (CollUtil.isEmpty(preemptionStockMap)) {
                return res;
            }

            List<ProductStockEntity> updateList = QtStreamUtil.filterAndMap(stockList,
                    stock -> preemptionStockMap.containsKey(stock.getSkuId()),
                    entity -> {
                        ProductStockEntity stock = new ProductStockEntity();
                        stock.setId(entity.getId());
                        Integer count = ObjectUtil.defaultIfNull(entity.getPreemptionStock(), 0);
                        stock.setPreemptionStock(preemptionStockMap.getOrDefault(entity.getSkuId(), 0) + count);
                        return stock;
                    });

            productStockService.updateBatchById(updateList);

        } finally {
            distributedLocker.unLockMulti(skuStockKeys);
        }

        return res;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    @SuppressWarnings("all")
    public void incrementPassageStocks(List<PassageStockDTO> passageStocks) {
        List<String> localSkuIds = QtStreamUtil.map(passageStocks, PassageStockDTO::getSkuId);
        List<String> skuStockKeys = QtStreamUtil.map(localSkuIds, CacheKeyAssist::getProductPassageStockLockKey);
        if (!distributedLocker.tryLockMulti(skuStockKeys)) {
            throw ExceptionFactory.getI18nBizException("product.passage.stock.concurrence.error");
        }
        try {
            List<ProductStockEntity> stockList = productStockService.listBySkuIds(localSkuIds,
                    ProductStockEntity::getId,
                    ProductStockEntity::getSkuId,
                    ProductStockEntity::getPassageStock);
            Map<String, ProductStockEntity> stockMap = QtStreamUtil.toMap(stockList, ProductStockEntity::getSkuId);

            List<ProductStockEntity> pendingUpdateStocks = QtStreamUtil.map(passageStocks,
                    passageStock -> {
                        // 累计在途库存,兼容sku 无库存 数据的情况
                        ProductStockEntity productStockEntity = QtOptionalUtil
                                .of(stockMap.get(passageStock.getSkuId()))
                                .orElse(ProductStockEntity.createDefaultStock(passageStock.getUserId(), passageStock.getSkuId(), ""));
                        Integer curCount = ObjectUtil.defaultIfNull(productStockEntity.getPassageStock(), 0);
                        productStockEntity.setPassageStock(curCount + passageStock.getSkuQuantity());
                        return productStockEntity;
                    });

            Map<Boolean, List<ProductStockEntity>> saveOrUpdateMap = QtStreamUtil.partition(pendingUpdateStocks,
                    stock -> Objects.nonNull(stock.getId()));

            QtOptionalUtil.of(saveOrUpdateMap.get(true))
                    .ifPresent(list -> productStockService.updateBatchById(list));

            QtOptionalUtil.of(saveOrUpdateMap.get(false))
                    .ifPresent(list -> productStockService.saveBatch(list));
        } finally {
            distributedLocker.unLockMulti(skuStockKeys);
        }


    }


    @SuppressWarnings("all")
    private MPJLambdaWrapper<LocalProductEntity> createStockQueryWrapper(StockQueryParam queryParam) {
        String sku = queryParam.getSku();
        String spu = queryParam.getSpu();
        String productEnTitle = queryParam.getProductEnTitle();
        boolean customerRole = queryParam.isCustomerRole();
        List<String> merchantUserIds = queryParam.getMerchantUserIds();
        String sortMode = queryParam.getSortMode();

        StockSortFieldEnum sortField = IEnumConvert.valueOf(queryParam.getSortField(), StockSortFieldEnum.class);
        boolean sorted = Objects.nonNull(sortField) && StringUtils.isNotBlank(sortMode);

        MPJLambdaWrapper<LocalProductEntity> wrapper = JoinWrappers.lambda(LocalProductEntity.class);
        wrapper.selectAs(LocalProductEntity::getSkuId, ProductStockResultEntity::getSkuId)
                .selectAs(LocalProductEntity::getSku, ProductStockResultEntity::getSku)
                .selectAs(LocalProductEntity::getSpu, ProductStockResultEntity::getSpu)
                .selectAs(LocalProductEntity::getProductEnTitle, ProductStockResultEntity::getProductEnTitle)
                .selectAs(LocalProductEntity::getImgUrl, ProductStockResultEntity::getImgUrl)
                .selectAs(UserEntity::getUserName, ProductStockResultEntity::getMerchantUserName)

                .selectFunc(StockFuncEnum.STOCK_NAME_IF_NULL, ProductStockEntity::getStashName)
                .selectFunc(StockFuncEnum.STOCK_COUNT_IF_NULL, ProductStockEntity::getPhysicalStock)
                .selectFunc(StockFuncEnum.STOCK_COUNT_IF_NULL, ProductStockEntity::getPreemptionStock)
                .selectFunc(StockFuncEnum.STOCK_COUNT_IF_NULL, ProductStockEntity::getFrozenStock)
                .selectFunc(StockFuncEnum.STOCK_COUNT_IF_NULL, ProductStockEntity::getPassageStock)
                .selectFunc(StockFuncEnum.STOCK_COUNT_IF_NULL, ProductStockEntity::getBadStock)
                .selectFunc(StockFuncEnum.STOCK_COUNT_IF_NULL, ProductStockEntity::getGoodStock)

                .leftJoin(ProductStockEntity.class, on ->
                        on.eq(LocalProductEntity::getSkuId, ProductStockEntity::getSkuId))
                .innerJoin(UserEntity.class, on ->
                        on.eq(LocalProductEntity::getUserId, UserEntity::getUserId))

                .eq(customerRole, LocalProductEntity::getUserId, queryParam.getUserId())
                .in(!customerRole && CollUtil.isNotEmpty(merchantUserIds),
                        LocalProductEntity::getUserId, merchantUserIds)
                .like(StringUtils.isNotBlank(sku), LocalProductEntity::getSku, sku)
                .like(StringUtils.isNotBlank(spu), LocalProductEntity::getSku, spu)
                .like(StringUtils.isNotBlank(productEnTitle), LocalProductEntity::getSku, productEnTitle);

        if (sorted) {
            wrapper.orderBy(sorted, QtMybatisPlusUtil.isAsc(sortMode), sortField.getColumn());
        }
        wrapper.orderByDesc(LocalProductEntity::getSkuId);

        return wrapper;


    }

    // 事务型预占本地产品库存，只要有一个产品预占失败则全部失败
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean transactionPreemptOrderLocalProductStocks( Map<String, Integer> skuIdToNumsMap ) {

        // 根据skuIds 加锁
        List<String> localSkuIds = skuIdToNumsMap.keySet().stream().collect(Collectors.toList());
        List<String> skuStockKeys = QtStreamUtil.map(localSkuIds, CacheKeyAssist::getProductPreemptionStockLockKey);
        if (!distributedLocker.tryLockMulti(skuStockKeys)) {
            throw ExceptionFactory.getI18nBizException("order.sync.concurrence.error");
        }

        try {
            // 查询 skuIds 的库存
            List<ProductStockEntity> stockList = productStockService.listBySkuIds(localSkuIds);
            if (CollUtil.isEmpty(stockList)) {
                return false;
            }

            // 创建预占库存计算器
            StockComputeExecutor executor = StockComputeExecutor
                    .create(StockCreator.create(stockList))
                    .preemption();

            // 批量执行预占所有sku库存
            boolean preemption = executor.executeMultiPreemption(skuIdToNumsMap);
            if (!preemption) {
                return false;
            }

            Map<String, Integer> preemptionStockMap = executor.getSkuPreemptionStockMap();
            if (CollUtil.isEmpty(preemptionStockMap)) {
                return false;
            }

            List<ProductStockEntity> updateList = QtStreamUtil.filterAndMap(stockList,
                    stock -> preemptionStockMap.containsKey(stock.getSkuId()),
                    entity -> {
                        ProductStockEntity stock = new ProductStockEntity();
                        stock.setId(entity.getId());
                        Integer count = ObjectUtil.defaultIfNull(entity.getPreemptionStock(), 0);
                        stock.setPreemptionStock(preemptionStockMap.getOrDefault(entity.getSkuId(), 0) + count);
                        return stock;
                    });

            productStockService.updateBatchById(updateList);

        } finally {
            distributedLocker.unLockMulti(skuStockKeys);
        }

        return true;
    }

    @Override
    public List<Object> pickUpRecords(String pickUpNo) {
        return physicalStockFlowMapper.listPickUps(pickUpNo);
    }

}
